﻿Imports System.Data.SqlServerCe

Friend Class ObjectPersister
    Public Shared Function GetOne(Of T)(ByVal whereClause As String) As T
        Dim clsTypeDef As TypeDefinition = GetTypeDef(GetType(T))

        Dim sqlCmd As SqlCeCommand = GenerateSelectCommand(clsTypeDef, whereClause, Nothing)

        sqlCmd.Connection.Open()
        Try
            Using dr As SqlCeDataReader = sqlCmd.ExecuteReader
                If dr.Read Then
                    Return DRToObject(Of T)(dr, clsTypeDef)
                Else
                    Return Nothing
                End If
            End Using
        Finally
            sqlCmd.Connection.Close()
        End Try
    End Function

    Public Shared Function GetAll(Of T)() As Generic.List(Of T)
        Return GetAllWorker(Of T)(Nothing, Nothing, Nothing)
    End Function

    Public Shared Function GetAll(Of T)(ByVal whereClause As String, ByVal ParamArray parameters() As SqlCeParameter) As Generic.List(Of T)
        Return GetAllWorker(Of T)(whereClause, Nothing, parameters)
    End Function

    Public Shared Function GetAll(Of T)(ByVal whereClause As String, ByVal orderByClause As String, ByVal ParamArray parameters() As SqlCeParameter) As Generic.List(Of T)
        Return GetAllWorker(Of T)(whereClause, orderByClause, parameters)
    End Function

    Private Shared Function GetAllWorker(Of T)(ByVal whereClause As String, ByVal orderByClause As String, ByVal parameters() As SqlCeParameter) As Generic.List(Of T)
        Dim clsTypeDef As TypeDefinition = GetTypeDef(GetType(T))

        Dim sqlCmd As SqlCeCommand = GenerateSelectCommand(clsTypeDef, whereClause, orderByClause)
        If parameters IsNot Nothing AndAlso parameters.Count > 0 Then
            sqlCmd.Parameters.AddRange(parameters)
        End If

        Dim aRet As New Generic.List(Of T)
        sqlCmd.Connection.Open()
        Try
            Using dr As SqlCeDataReader = sqlCmd.ExecuteReader
                While dr.Read
                    aRet.Add(DRToObject(Of T)(dr, clsTypeDef))
                End While
            End Using
        Finally
            sqlCmd.Connection.Close()
        End Try
        Return aRet
    End Function

    Private Shared Function GenerateSelectCommand(ByVal typeDef As TypeDefinition, ByVal whereClause As String, ByVal orderByClause As String) As SqlCeCommand
        Dim clsSql As New System.Text.StringBuilder(typeDef.SelectSqlStatement)
        If Not String.IsNullOrEmpty(whereClause) Then
            clsSql.Append(" WHERE ")
            clsSql.Append(whereClause)
        End If
        If Not String.IsNullOrEmpty(orderByClause) Then
            clsSql.Append(" ORDER BY ")
            clsSql.Append(orderByClause)
        End If

        Return Environment.NewCommand(clsSql.ToString)
    End Function

    Public Shared Sub DefineObject(ByVal type As Type, ByVal tableName As String, ByVal columns() As String)
        Dim sName As String = type.Name
        If mTypeDefinitions.ContainsKey(sName) Then Return

        Dim clsDef As New TypeDefinition
        clsDef.FieldCount = columns.Count
        clsDef.Type = type
        clsDef.TableName = tableName
        System.Array.Resize(clsDef.ColumnFieldInfos, columns.Count)
        System.Array.Resize(clsDef.CustomSetHandlers, columns.Count)
        Dim clsSql As New System.Text.StringBuilder("SELECT ")
        For i As Integer = 0 To columns.Count - 1
            Dim sColDef As String = columns(i)
            Dim sTableColName As String
            Dim sFieldName As String
            Dim iPos As Integer = sColDef.IndexOf("/"c)
            If iPos > 0 Then
                sTableColName = sColDef.Substring(0, iPos)
                sFieldName = sColDef.Substring(iPos + 1)
            Else
                sTableColName = sColDef
                sFieldName = sColDef
            End If

            If i = 0 Then
                clsSql.Append("[")
            Else
                clsSql.Append(",[")
            End If
            clsSql.Append(sTableColName)
            clsSql.Append("]"c)
            clsDef.ColumnFieldInfos(i) = type.GetField(sFieldName)
            If clsDef.ColumnFieldInfos(i) Is Nothing Then
                Throw New ArgumentException(String.Format("Field {0} not found on type {1}", sFieldName, sName))
            End If
        Next
        clsSql.Append(" FROM ")
        clsSql.Append(tableName)
        clsDef.SelectSqlStatement = clsSql.ToString
        mTypeDefinitions.Add(sName, clsDef)
    End Sub
    Public Delegate Sub SetCustomFieldDelegate(ByVal obj As Object, ByVal value As Object)
    'Public Delegate Function GetCustomFieldDelegate(ByVal obj As Object) As Object
    Public Shared Sub AddCustomFieldHandler(ByVal type As System.Type, ByVal fieldName As String, ByVal setHandler As SetCustomFieldDelegate)
        Dim clsTD As TypeDefinition = GetTypeDef(type)
        If clsTD Is Nothing Then Throw New ApplicationException("Type not defined!")

        For i As Integer = 0 To clsTD.ColumnFieldInfos.Count - 1
            If clsTD.ColumnFieldInfos(i).Name = fieldName Then
                clsTD.CustomSetHandlers(i) = setHandler
                Return
            End If
        Next
        Throw New ApplicationException("Field not found!")
    End Sub

    Private Shared Function GetTypeDef(ByVal type As System.Type) As TypeDefinition
        Return mTypeDefinitions.Item(type.Name)
    End Function

    Private Shared mTypeDefinitions As New Generic.Dictionary(Of String, TypeDefinition)
    Private Class TypeDefinition
        Public Type As System.Type
        Public SelectSqlStatement As String
        Public FieldCount As Integer
        Public TableName As String
        Public ColumnFieldInfos() As System.Reflection.FieldInfo
        Public CustomSetHandlers() As SetCustomFieldDelegate
    End Class

    Private Shared Function DRToObject(Of T)(ByVal dr As SqlCeDataReader, ByVal typeDef As TypeDefinition) As T
        Dim clsType As System.Type = GetType(T)
        Dim clsRet As T = CType(System.Activator.CreateInstance(GetType(T)), T)
        For i As Integer = 0 To typeDef.FieldCount - 1
            Dim oValue As Object = dr.GetValue(i)
            If oValue IsNot DBNull.Value Then
                If typeDef.CustomSetHandlers(i) Is Nothing Then
                    typeDef.ColumnFieldInfos(i).SetValue(clsRet, oValue)
                Else
                    typeDef.CustomSetHandlers(i).Invoke(clsRet, oValue)
                End If
            End If
        Next
        Return clsRet
    End Function
End Class